{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 基于官方baseline修改，acc94左右"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 本baseline采用pytorch框架，应用ModelArts的Notebook进行开发"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 数据集获取\n",
    "将您OBS桶中的数据文件加载到此notebook中，将如下代码中\"obs-aifood-baseline\"修改成您OBS桶名称。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:root:Using MoXing-v1.14.1-ddfd6c9a\n",
      "INFO:root:Using OBS-Python-SDK-3.1.2\n",
      "INFO:root:Listing OBS: 1000\n",
      "INFO:root:Listing OBS: 2000\n",
      "INFO:root:Listing OBS: 3000\n",
      "INFO:root:Listing OBS: 4000\n",
      "INFO:root:Listing OBS: 5000\n",
      "INFO:root:pid: None.\t1000/5001\n",
      "INFO:root:pid: None.\t2000/5001\n",
      "INFO:root:pid: None.\t3000/5001\n",
      "INFO:root:pid: None.\t4000/5001\n",
      "INFO:root:pid: None.\t5000/5001\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "done\n"
     ]
    }
   ],
   "source": [
    "import moxing as mox\n",
    "# 导入数据\n",
    "mox.file.copy_parallel('s3://obs-aifood-bj4/aifood','./aifood/')\n",
    "\n",
    "# 为了方便使用预训练模型，这里已经手动下载了resnet50预训练模型存在自己的OBS桶里\n",
    "# 下载地址：https://download.pytorch.org/models/resnet50-19c8e357.pth\n",
    "mox.file.copy_parallel('s3://c4ai/premodel/resnet50-19c8e357.pth',\n",
    "                       './resnet50-19c8e357.pth')\n",
    "print(\"done\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "aifood\tresnet50-19c8e357.pth\r\n"
     ]
    }
   ],
   "source": [
    "!ls "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 加载依赖"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "from __future__ import print_function, division\n",
    "\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "from torch.optim import lr_scheduler\n",
    "from torch.autograd import Variable\n",
    "import torchvision\n",
    "from torchvision import datasets, models, transforms\n",
    "import time\n",
    "import os\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 加载数据集，并将其分为训练集和测试集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'三明治': 0, '冰激凌': 1, '土豆泥': 2, '小米粥': 3, '松鼠鱼': 4, '烤冷面': 5, '玉米饼': 6, '甜甜圈': 7, '芒果班戟': 8, '鸡蛋布丁': 9}\n",
      "{'train': 4500, 'val': 500}\n"
     ]
    }
   ],
   "source": [
    "#使用imagenet的均值方差\n",
    "mean,std = [0.485, 0.456, 0.406], [0.229, 0.224, 0.225]\n",
    "\n",
    "# 使用了图像增强，这里加了翻转仿射变换，这里可能会导致验证集的图片也被增强\n",
    "# 我在推理文件中也加了数据增强，不确定是否有影响，推理文件看最后两个代码块\n",
    "dataTrans = transforms.Compose([\n",
    "            transforms.Resize(256),\n",
    "            transforms.CenterCrop(224),\n",
    "            transforms.RandomHorizontalFlip(),\n",
    "            transforms.RandomAffine(degrees=5, translate=(0.05, 0.05), scale=(0.95, 1.05)),\n",
    "            transforms.ToTensor(),\n",
    "            transforms.Normalize(mean, std)\n",
    "        ])\n",
    " \n",
    "    # image data path\n",
    "data_dir = './aifood/images'\n",
    "all_image_datasets = datasets.ImageFolder(data_dir, dataTrans)\n",
    "print(all_image_datasets.class_to_idx) \n",
    "# 0.1作为验证集\n",
    "trainsize = int(0.9*len(all_image_datasets))\n",
    "testsize = len(all_image_datasets) - trainsize\n",
    "train_dataset, test_dataset = torch.utils.data.random_split(all_image_datasets,[trainsize,testsize])\n",
    "   \n",
    "image_datasets = {'train':train_dataset,'val':test_dataset}\n",
    "    \n",
    "\n",
    "    # wrap your data and label into Tensor\n",
    "\n",
    "    \n",
    "dataloders = {x: torch.utils.data.DataLoader(image_datasets[x],\n",
    "                                                 batch_size=32,\n",
    "                                                 shuffle=True,\n",
    "                                                 num_workers=4) for x in ['train', 'val']}\n",
    "\n",
    "dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}\n",
    "\n",
    "    # use gpu or not\n",
    "use_gpu = torch.cuda.is_available()\n",
    "\n",
    "print(dataset_sizes)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "def train_model(model, lossfunc, optimizer, scheduler, num_epochs=10):\n",
    "    start_time = time.time()\n",
    "\n",
    "    best_model_wts = model.state_dict()\n",
    "    best_acc = 0.0\n",
    "\n",
    "    for epoch in range(num_epochs):\n",
    "        print('Epoch {}/{}'.format(epoch, num_epochs - 1))\n",
    "        print('-' * 10)\n",
    "\n",
    "        # Each epoch has a training and validation phase\n",
    "        for phase in ['train', 'val']:\n",
    "            if phase == 'train':\n",
    "                scheduler.step()\n",
    "                model.train(True)  # Set model to training mode\n",
    "            else:\n",
    "                model.train(False)  # Set model to evaluate mode\n",
    "\n",
    "            running_loss = 0.0\n",
    "            running_corrects = 0.0\n",
    "\n",
    "            # Iterate over data.\n",
    "            for data in dataloders[phase]:\n",
    "                # get the inputs\n",
    "                inputs, labels = data\n",
    "                \n",
    "\n",
    "                # wrap them in Variable\n",
    "                if use_gpu:\n",
    "                    inputs = Variable(inputs.cuda())\n",
    "                    labels = Variable(labels.cuda())\n",
    "                else:\n",
    "                    inputs, labels = Variable(inputs), Variable(labels)\n",
    "\n",
    "                # zero the parameter gradients\n",
    "                optimizer.zero_grad()\n",
    "\n",
    "                # forward\n",
    "                outputs = model(inputs)\n",
    "                _, preds = torch.max(outputs.data, 1)\n",
    "                loss = lossfunc(outputs, labels)\n",
    "\n",
    "                # backward + optimize only if in training phase\n",
    "                if phase == 'train':\n",
    "                    loss.backward()\n",
    "                    optimizer.step()\n",
    "\n",
    "                # statistics\n",
    "                running_loss += loss.data\n",
    "                running_corrects += torch.sum(preds == labels.data).to(torch.float32)\n",
    "\n",
    "            epoch_loss = running_loss / dataset_sizes[phase]\n",
    "            epoch_acc = running_corrects / dataset_sizes[phase]\n",
    "\n",
    "            print('{} Loss: {:.4f} Acc: {:.4f}'.format(\n",
    "                phase, epoch_loss, epoch_acc))\n",
    "\n",
    "            # deep copy the model\n",
    "            if phase == 'val' and epoch_acc > best_acc:\n",
    "                best_acc = epoch_acc\n",
    "                best_model_wts = model.state_dict()\n",
    "\n",
    "    elapsed_time = time.time() - start_time\n",
    "    print('Training complete in {:.0f}m {:.0f}s'.format(\n",
    "        elapsed_time // 60, elapsed_time % 60))\n",
    "    print('Best val Acc: {:4f}'.format(best_acc))\n",
    "\n",
    "    # load best model weights\n",
    "    model.load_state_dict(best_model_wts)\n",
    "  \n",
    "    return model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 模型训练\n",
    "采用resnet50神经网络结构训练模型,模型训练需要一定时间，等待该段代码运行完成后再往下执行。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 0/9\n",
      "----------\n",
      "train Loss: 0.0347 Acc: 0.6609\n",
      "val Loss: 0.0124 Acc: 0.8900\n",
      "Epoch 1/9\n",
      "----------\n",
      "train Loss: 0.0112 Acc: 0.8913\n",
      "val Loss: 0.0086 Acc: 0.9200\n",
      "Epoch 2/9\n",
      "----------\n",
      "train Loss: 0.0076 Acc: 0.9260\n",
      "val Loss: 0.0086 Acc: 0.9240\n",
      "Epoch 3/9\n",
      "----------\n",
      "train Loss: 0.0057 Acc: 0.9462\n",
      "val Loss: 0.0064 Acc: 0.9320\n",
      "Epoch 4/9\n",
      "----------\n",
      "train Loss: 0.0035 Acc: 0.9678\n",
      "val Loss: 0.0061 Acc: 0.9340\n",
      "Epoch 5/9\n",
      "----------\n",
      "train Loss: 0.0037 Acc: 0.9629\n",
      "val Loss: 0.0066 Acc: 0.9360\n",
      "Epoch 6/9\n",
      "----------\n",
      "train Loss: 0.0034 Acc: 0.9693\n",
      "val Loss: 0.0064 Acc: 0.9380\n",
      "Epoch 7/9\n",
      "----------\n",
      "train Loss: 0.0035 Acc: 0.9667\n",
      "val Loss: 0.0057 Acc: 0.9400\n",
      "Epoch 8/9\n",
      "----------\n",
      "train Loss: 0.0033 Acc: 0.9731\n",
      "val Loss: 0.0059 Acc: 0.9440\n",
      "Epoch 9/9\n",
      "----------\n",
      "train Loss: 0.0033 Acc: 0.9687\n",
      "val Loss: 0.0062 Acc: 0.9440\n",
      "Training complete in 4m 25s\n",
      "Best val Acc: 0.944000\n"
     ]
    }
   ],
   "source": [
    "# get model and replace the original fc layer with your fc layer\n",
    "\n",
    "model_ft = models.resnet50(pretrained=False)\n",
    "# 加载已经下载的预训练模型\n",
    "model_ft.load_state_dict(torch.load('resnet50-19c8e357.pth'))\n",
    "\n",
    "num_ftrs = model_ft.fc.in_features\n",
    "\n",
    "# 加入了dropout，避免过拟合\n",
    "model_ft.fc = nn.Sequential(nn.Dropout(0.5),nn.Linear(num_ftrs, 10))\n",
    "\n",
    "# model_ft.fc = nn.Linear(num_ftrs, 10)\n",
    "\n",
    "if use_gpu:\n",
    "    model_ft = model_ft.cuda()\n",
    "\n",
    "    # define loss function\n",
    "lossfunc = nn.CrossEntropyLoss()\n",
    "\n",
    "# setting optimizer and trainable parameters\n",
    "\n",
    "# 这里直接训练所有层的参数\n",
    "params = list(model_ft.parameters())\n",
    "\n",
    "# 加了L2正则化，防止过拟合\n",
    "optimizer_ft = optim.SGD(params, lr=0.001, momentum=0.9, weight_decay=0.0005)\n",
    "# optimizer_ft = optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0.0005)\n",
    "\n",
    "\n",
    "# Decay LR by a factor of 0.1 every 4 epochs\n",
    "# 每4个epch减小学习率，我是这么理解的\n",
    "    \n",
    "exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=4, gamma=0.1)\n",
    "\n",
    "model_ft = train_model(model=model_ft,\n",
    "                           lossfunc=lossfunc,\n",
    "                           optimizer=optimizer_ft,\n",
    "                           scheduler=exp_lr_scheduler,\n",
    "                           num_epochs=10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "将训练好的模型保存下来。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "torch.save(model_ft.state_dict(), './model.pth')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 将训练好的模型保存至OBS\n",
    "将模型保存到OBS桶中model文件夹下，为后续推理测试、模型提交做准备。将如下代码中\"obs-aifood-baseline\"修改成您OBS桶的名称。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "done\n"
     ]
    }
   ],
   "source": [
    "import moxing as mox\n",
    "# 将c4ai替换成自己的桶名称，\n",
    "mox.file.copy('./model.pth','s3://c4ai/model_output_re50/model/resnet-50.pth')\n",
    "print(\"done\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 下面是推理文件的改动部分：\n",
    "# 1数据处理部分,这里不确定必须要改，大佬们可以自行尝试\n",
    "# 2网络结构部分\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 代码位置在25行左右\n",
    "\n",
    "# 这里均值方差使用数据处理部分使用一致\n",
    "mean,std = [0.485, 0.456, 0.406], [0.229, 0.224, 0.225]\n",
    "infer_transformation = transforms.Compose([\n",
    "    transforms.Resize(256),\n",
    "#     transforms.Resize((224,224)),\n",
    "    transforms.CenterCrop(224),\n",
    "    \n",
    "\n",
    "    transforms.RandomHorizontalFlip(),\n",
    "    transforms.RandomAffine(degrees=5, translate=(0.05, 0.05), scale=(0.95, 1.05)),\n",
    "\n",
    "    \n",
    "    transforms.ToTensor(),\n",
    "    transforms.Normalize(mean, std)\n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 代码位置在158行左右\n",
    "\n",
    "def resnet50(model_path, **kwargs):\n",
    "\n",
    "    \"\"\"Constructs a ResNet-50 model.\n",
    "    Args:\n",
    "        pretrained (bool): If True, returns a model pre-trained on ImageNet\n",
    "    \"\"\"\n",
    "    model = models.resnet50(pretrained=False)\n",
    "    num_ftrs = model.fc.in_features\n",
    "    \n",
    "\n",
    "    model.fc = nn.Sequential(nn.Dropout(0.5),nn.Linear(num_ftrs, 10))\n",
    "\n",
    "\n",
    "#     model.fc = nn.Linear(num_ftrs, 10)\n",
    "    model.load_state_dict(torch.load(model_path,map_location ='cpu'))\n",
    "    # model.load_state_dict(torch.load(model_path))\n",
    "\n",
    "    model.eval()\n",
    "\n",
    "    return model"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Pytorch-1.0.0",
   "language": "python",
   "name": "pytorch-1.0.0"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
